package net.simflow.swf.analyzer.network.opt;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import net.simflow.swf.analyzer.network.data.Flow;
import net.simflow.swf.analyzer.network.data.Instance;
import net.simflow.swf.analyzer.network.data.Network;
import net.simflow.swf.analyzer.network.data.Node;
import net.simflow.swf.analyzer.network.data.Service;

public class ServiceNetworkMerger {

	private static ServiceNetworkMerger instance = new ServiceNetworkMerger();

	private ServiceNetworkMerger() {
	}

	public static ServiceNetworkMerger getInstance() {
		return instance;
	}

	private HashSet<Instance> insSet = new HashSet<Instance>();
	private HashSet<Service> svcSet = new HashSet<Service>();
	private HashSet<Node> snSet = new HashSet<Node>();

	public Network merge(Network network) {
		if (network == null) {
			return null;
		}

		try {
			Network snw = new Network();

			this.merge(network.getNodeSet());
			snw.addAllNode(this.snSet);
			for (Service svc : this.svcSet) {
				snw.addService(svc);
			}
			snw.addAllInstance(this.insSet);
			this.clearSets();

			for (String fName : network.getFlowMap().keySet()) {
				Flow f = network.getFlowMap().get(fName);
				Flow sf = this.merge(f);
				if (!sf.getNodeSet().isEmpty()) {
					snw.addFlow(sf, fName);
				}
			}

			NumberFormat format = NumberFormat.getIntegerInstance();
			format.setMinimumIntegerDigits(5);
			format.setGroupingUsed(false);
			int c;

			c = 0;
			for (Node n : snw.getNodeSet()) {
				n.setNameInNetwork("Node" + format.format(c));
				c++;
			}

			for (Flow f : snw.getFlowMap().values()) {
				for (Node n : f.getNodeSet()) {
					n.setNameInNetwork("Node" + format.format(c));
					c++;
				}
			}

			return snw;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Flow merge(Flow flow) {
		if (flow == null) {
			return null;
		}

		Flow sflow = new Flow(flow.getName(), flow.getUri());
		this.merge(flow.getNodeSet());
		sflow.addNodeSet(this.snSet);
		sflow.addServiceSet(this.svcSet);
		sflow.addInstanceSet(this.insSet);
		this.clearSets();

		for (Node s : sflow.getNodeSet()) {
			if (s.getParentDataSet().isEmpty()
					&& s.getParentControlSet().isEmpty()) {
				sflow.addEntrance(s);
			}
			if (s.getChildDataSet().isEmpty()
					&& s.getChildControlSet().isEmpty()) {
				sflow.addExit(s);
			}
		}
		return sflow;
	}

	private void merge(HashSet<Node> ns) {
		this.clearSets();

		HashMap<Node, Node> nsmap = new HashMap<Node, Node>();

		for (Node n : ns) {
			Node sn = new Node(n.getService());
			nsmap.put(n, sn);
		}

		for (Node n : ns) {
			Node sn = nsmap.get(n);
			for (Node p : n.getParentDataSet()) {
				Node sp = nsmap.get(p);
				sn.addParentData(sp);
				sp.addChildData(sn);
			}
			for (Node p : n.getParentControlSet()) {
				Node sp = nsmap.get(p);
				sn.addParentControl(sp);
				sp.addChildControl(sn);
			}
			for (Node c : n.getChildDataSet()) {
				Node sc = nsmap.get(c);
				sc.addParentData(sn);
				sn.addChildData(sc);
			}
			for (Node c : n.getChildControlSet()) {
				Node sc = nsmap.get(c);
				sc.addParentControl(sn);
				sn.addChildControl(sc);
			}
		}

		ArrayList<Node> sa = new ArrayList<Node>(nsmap.values());
		while (!sa.isEmpty()) {
			Node s = sa.get(0);
			if (s.getService().getWsdlURI() != null) {
				this.snSet.add(s);
				this.svcSet.add(s.getService());
				this.insSet.addAll(s.getService().getInstanceSet());
			} else {
				for (Node p : s.getParentDataSet()) {
					p.addChildDataSet(s.getChildDataSet());
					p.getChildDataSet().remove(s);
				}
				for (Node p : s.getParentControlSet()) {
					p.addChildControlSet(s.getChildControlSet());
					p.getChildControlSet().remove(s);
				}
				for (Node c : s.getChildDataSet()) {
					c.addParentDataSet(s.getParentDataSet());
					c.getParentDataSet().remove(s);
				}
				for (Node c : s.getChildControlSet()) {
					c.addParentControlSet(s.getParentControlSet());
					c.getParentControlSet().remove(s);
				}
			}
			sa.remove(s);
		}

		for (Node s : this.snSet) {
			s.getParentDataSet().remove(s);
			s.getParentControlSet().remove(s);
			s.getChildDataSet().remove(s);
			s.getChildControlSet().remove(s);
		}
	}

	private void clearSets() {
		this.insSet.clear();
		this.svcSet.clear();
		this.snSet.clear();

	}
}
